﻿using System;
using System.Collections.Generic;
using System.Text;

namespace tysila
{
    class Layout
    {
        public static Layout CreateLayout(Metadata.TypeDefRow tdr, Signature.BaseOrComplexType sig, Assembler ass,
            IList<Assembler.TypeToCompile> new_types)
        {
            Layout l = new Layout();

            Layout2 l2 = new Layout2();
            l2.CreateLayout(new Assembler.TypeToCompile { type = tdr, tsig = new Signature.Param(sig, ass) }, new_types, ass);

            l.CreateObjectLayout(tdr, sig, ass, new_types);

            if ((sig is Signature.BoxedType) || (!tdr.IsValueType(ass)))
            {
                l.CreateVtblLayout(tdr, sig, ass, new_types);
                l.CreateCastTableLayout(tdr, sig, ass, new_types);
            }

            return l;
        }

        private void CreateCastTableLayout(Metadata.TypeDefRow tdr, Signature.BaseOrComplexType sig, Assembler ass,
            IList<Assembler.TypeToCompile> new_types)
        {
            CastTableLayout = new List<CastTableItem>();

            int offset = 0;
            CastTableLayout.Add(new CastTableLength { length = BCLayout.Count, offset = offset });
            offset += ass.GetSizeOf(new Signature.Param(BaseType_Type.I));

            foreach (KeyValuePair<Metadata.TypeDefRow, BCOffsetSig> bcl in BCLayout)
            {
                CastTableLayout.Add(new CastInfo
                {
                    offset = offset,
                    typeinfo_name = Mangler.Mangle(bcl.Key, bcl.Value.sig) + "_TI",
                    offset_from_top = bcl.Value.offset,
                    ttc = new Assembler.TypeToCompile { tsig = new Signature.Param(bcl.Value.sig, ass), type = bcl.Key }
                });

                if (new_types != null)
                    new_types.Add(new Assembler.TypeToCompile { tsig = new Signature.Param(bcl.Value.sig, ass),
                        type = bcl.Key });
            }
        }

        private void CreateVtblLayout(Metadata.TypeDefRow tdr, Signature.BaseOrComplexType sig, Assembler ass,
            IList<Assembler.TypeToCompile> new_types)
        {
            VTableLayout = new List<VTblItem>();

            VTLayout.Add(tdr, 0);

            int offset = 0;
            VTableLayout.Add(new TypeInfoPointer { typeinfo_name = Mangler.Mangle(tdr, sig) + "_TI", offset = offset, tdr = tdr });
            offset += ass.GetSizeOf(new Signature.Param(BaseType_Type.I));
            VTableLayout.Add(new OffsetToTop { ott = 0, offset = offset });
            offset += ass.GetSizeOf(new Signature.Param(BaseType_Type.I));
            VTableLayout.Add(new CastTablePointer { casttable_name = Mangler.Mangle(tdr, sig) + "_CT", offset = offset });
            offset += ass.GetSizeOf(new Signature.Param(BaseType_Type.I));

            Dictionary<string, Metadata.MethodDefRow> overrides = new Dictionary<string,Metadata.MethodDefRow>();

            for (Metadata.TableIndex ti = tdr.MethodList; ti < Metadata.GetLastMethod(tdr.m, tdr); ti++)
            {
                Metadata.MethodDefRow mdr = ti.Value as Metadata.MethodDefRow;
                Signature.BaseMethod bmeth = Signature.ParseMethodDefSig(mdr.m, mdr.Signature);

                if (mdr.IsVirtual)
                {
                    // Need to define a vtable position for this method
                    if (mdr.IsNewSlot)
                    {
                        VMethLayout.Add(Mangler.MethodOnlyMangle(mdr), new VMethItem { offset = offset,
                            defined_in = mdr.IsAbstract ? null : tdr, tsig = sig, mdr = mdr, bmeth = bmeth });
                        VTableLayout.Add(new FunctionPointer
                        {
                            offset = offset,
                            func_name = mdr.IsAbstract ? Assembler.PureVirtualSig : Mangler.Mangle(tdr, sig, mdr, bmeth),
                            this_adjust = 0
                        });
                        offset += 2 * ass.GetSizeOf(new Signature.Param(BaseType_Type.I));
                    }
                    else if(!mdr.IsAbstract)
                    {
                        // this method overrides an existing slot
                        overrides.Add(Mangler.MethodOnlyMangle(mdr), mdr);
                    }
                }
            }

            if (tdr.Extends.Value != null)
            {
                Layout baselayout = Metadata.GetTypeDef(tdr.Extends.ToToken(), ass).GetLayout(new Signature.Param(new Token(tdr.Extends.Value), ass).Type, ass, null);

                foreach(KeyValuePair<string, VMethItem> vml in baselayout.VMethLayout)
                {
                    if (overrides.ContainsKey(vml.Key))
                    {
                        VMethLayout.Add(vml.Key, new VMethItem { defined_in = tdr, offset = offset, tsig = sig,
                            mdr = vml.Value.mdr, bmeth = vml.Value.bmeth });
                        VTableLayout.Add(new FunctionPointer
                        {
                            func_name = Mangler.Mangle(tdr, sig) + vml.Key,
                            offset = offset,
                            this_adjust = 0
                        });
                        offset += 2 * ass.GetSizeOf(new Signature.Param(BaseType_Type.I));
                    }
                    else
                    {
                        VMethLayout.Add(vml.Key, new VMethItem { defined_in = vml.Value.defined_in, offset = offset,
                         tsig = vml.Value.tsig, mdr = vml.Value.mdr, bmeth = vml.Value.bmeth });
                        VTableLayout.Add(new FunctionPointer
                        {
                            func_name = (vml.Value.defined_in == null) ? Assembler.PureVirtualSig : 
                                Mangler.Mangle(vml.Value.defined_in, vml.Value.tsig) + vml.Key,
                            offset = offset,
                            this_adjust = (vml.Value.defined_in == null) ? 0 : BCLayout[vml.Value.defined_in].offset
                        });
                        offset += 2 * ass.GetSizeOf(new Signature.Param(BaseType_Type.I));
                    }
                }
            }
            else if (overrides.Count > 0)
                throw new Exception("virtual method overrides but no base class");
            
            // Now emit the vtable layout for base classes in derived

            Metadata.TypeDefRow curtdr = tdr;
            while (curtdr.Extends.Value != null)
            {
                Metadata.TypeDefRow newcurtdr = Metadata.GetTypeDef(new Token(curtdr.Extends.Value), ass);
                Layout baselayout = newcurtdr.GetLayout(new Signature.Param(new Token(curtdr.Extends.Value), ass).Type, ass, new_types);
                curtdr = newcurtdr;

                VTLayout.Add(curtdr, offset);

                VTableLayout.Add(new TypeInfoPointer { typeinfo_name = Mangler.Mangle(tdr, sig) + "_TI", offset = offset, tdr = tdr });
                offset += ass.GetSizeOf(new Signature.Param(BaseType_Type.I));
                VTableLayout.Add(new OffsetToTop { ott = -BCLayout[curtdr].offset, offset = offset });
                offset += ass.GetSizeOf(new Signature.Param(BaseType_Type.I));
                VTableLayout.Add(new CastTablePointer { casttable_name = Mangler.Mangle(tdr, sig) + "_CT", offset = offset });
                offset += ass.GetSizeOf(new Signature.Param(BaseType_Type.I));

                foreach (KeyValuePair<string, VMethItem> vml in baselayout.VMethLayout)
                {
                    VTableLayout.Add(new FunctionPointer
                    {
                        func_name = (vml.Value.defined_in == null) ? Assembler.PureVirtualSig :
                            Mangler.Mangle(vml.Value.defined_in, vml.Value.tsig) + vml.Key,
                        offset = offset,
                        this_adjust = (vml.Value.defined_in == null) ? 0 : 
                            BCLayout[vml.Value.defined_in].offset - BCLayout[curtdr].offset
                    });
                    offset += 2 * ass.GetSizeOf(new Signature.Param(BaseType_Type.I));
                }
            }
        }

        private void CreateObjectLayout(Metadata.TypeDefRow tdr, Signature.BaseOrComplexType sig, Assembler ass,
            IList<Assembler.TypeToCompile> new_types)
        {
            ClassSize = 0;
            StaticClassSize = 0;
            ObjectLayout = new List<ObjectItem>();
            FieldOffsets = new Dictionary<string, int>();
            int offset = 0;
            int static_offset = 0;
            if((sig is Signature.BoxedType) || (!tdr.IsValueType(ass)))
                ObjectLayout.Add(new VtblPointer { vtbl_for = tdr, offset = offset });
            offset += ass.GetSizeOf(new Signature.Param(BaseType_Type.I));

            BCLayout.Add(tdr, new BCOffsetSig { offset = 0, sig = sig });

            // Add a field for the vtable pointer and object id for the base class System.Object
            if (Mangler.Mangle(tdr, sig) == "_N6_System_T6_Object_S2_1C")
            {
                Metadata.FieldRow fr = new Metadata.FieldRow
                    {
                        Flags = 0x621,
                        m = null,
                        Name = "__object_id",
                        RowNumber = -1,
                        Signature = new byte[] { 0x06, 0x08 }
                    };

                ObjectLayout.Add(new Field
                {
                    field = fr,
                    offset = offset
                });
                FieldOffsets.Add(Mangler.FieldOnlyMangle(fr), offset);
                offset += ass.GetSizeOf(new Signature.Param(BaseType_Type.I4));
            }

            for (Metadata.TableIndex ti = tdr.FieldList; ti < Metadata.GetLastField(tdr.m, tdr); ti++)
            {
                Metadata.FieldRow f = ti.Value as Metadata.FieldRow;
                
                Signature.Param resolvedparam = Signature.ResolveGenericParam(
                    Signature.ParseFieldSig(((Metadata.FieldRow)ti.Value).m,
                    ((Metadata.FieldRow)ti.Value).Signature).AsParam, sig, ass);

                if (f.IsStatic)
                {
                    FieldOffsets.Add(Mangler.FieldOnlyMangle(ti.Value as Metadata.FieldRow), static_offset);
                    static_offset += ass.GetSizeOf(resolvedparam);
                }
                else
                {
                    ObjectLayout.Add(new Field
                    {
                        field = ti.Value as Metadata.FieldRow,
                        offset = offset
                    });
                    FieldOffsets.Add(Mangler.FieldOnlyMangle(ti.Value as Metadata.FieldRow), offset);
                    offset += ass.GetSizeOf(resolvedparam);
                }
            }

            ClassSize += offset;
            StaticClassSize += static_offset;

            if ((sig is Signature.BoxedType) || (!tdr.IsValueType(ass)))
            {
                if (tdr.Extends.Value != null)
                {
                    Signature.Param p_ext = new Signature.Param(new Token(tdr.Extends.Value), ass);
                    if ((sig is Signature.BoxedType) && (p_ext.Type is Signature.ComplexType) && (((Signature.ComplexType)p_ext.Type).IsValueType))
                        p_ext = new Signature.Param { Type = new Signature.BoxedType(p_ext.Type) };
                    Layout baselayout = Metadata.GetTypeDef(tdr.Extends.ToToken(), ass).GetLayout(p_ext.Type, ass, new_types);
                    ClassSize += baselayout.ClassSize;

                    foreach (KeyValuePair<Metadata.TypeDefRow, BCOffsetSig> bcl in baselayout.BCLayout)
                        BCLayout.Add(bcl.Key, new BCOffsetSig { offset = offset + bcl.Value.offset, sig = bcl.Value.sig });

                    foreach (ObjectItem oi in baselayout.ObjectLayout)
                    {
                        ObjectItem newoi;

                        if (oi is VtblPointer)
                        {
                            VtblPointer vpt = oi as VtblPointer;
                            newoi = new VtblPointer();
                            ((VtblPointer)newoi).vtbl_for = vpt.vtbl_for;
                        }
                        else if (oi is Field)
                        {
                            newoi = new Field { field = ((Field)oi).field };
                            FieldOffsets.Add(Mangler.FieldOnlyMangle(((Field)oi).field), oi.offset + offset);
                        }
                        else
                            throw new NotSupportedException();

                        newoi.offset = oi.offset + offset;
                        ObjectLayout.Add(newoi);
                    }
                }
            }
        }

        public struct BCOffsetSig { public int offset; public Signature.BaseOrComplexType sig; }
        public Dictionary<Metadata.TypeDefRow, BCOffsetSig> BCLayout = new Dictionary<Metadata.TypeDefRow, BCOffsetSig>();
        public Dictionary<Metadata.TypeDefRow, int> VTLayout = new Dictionary<Metadata.TypeDefRow,int>();
        public struct VMethItem { public int offset; public Metadata.TypeDefRow defined_in; public Signature.BaseOrComplexType tsig;
        public Metadata.MethodDefRow mdr; public Signature.BaseMethod bmeth; 
        }
        public Dictionary<string, VMethItem> VMethLayout = new Dictionary<string, VMethItem>();


        public class ObjectItem { public int offset = 0; }
        public class VtblPointer : ObjectItem { public Metadata.TypeDefRow vtbl_for; }
        public class Field : ObjectItem { public Metadata.FieldRow field; }

        public List<ObjectItem> ObjectLayout;
        public Dictionary<string, int> FieldOffsets = new Dictionary<string, int>();

        public class VTblItem { public int offset = 0; }
        public class TypeInfoPointer : VTblItem { public string typeinfo_name; public Metadata.TypeDefRow tdr; }
        public class OffsetToTop : VTblItem { public int ott; }
        public class CastTablePointer : VTblItem { public string casttable_name; }
        public class FunctionPointer : VTblItem
        {
            public string func_name;
            public int this_adjust;
        }
        /* public class VCallAdjust : VTblItem { public int adjust; }
        public class VFuncPtr : VTblItem { public string vfunc; } */

        public List<VTblItem> VTableLayout;

        public class CastTableItem { public int offset = 0; }
        public class CastTableLength : CastTableItem { public int length; }
        public class CastInfo : CastTableItem { public string typeinfo_name; public int offset_from_top;
        public Assembler.TypeToCompile ttc; }

        public List<CastTableItem> CastTableLayout;

        public int ClassSize = 0;
        public int StaticClassSize = 0;
    }
}
